﻿// 
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
// 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Reflection;
using NetworkCommsDotNet.Tools;

#if NETFX_CORE
using System.Linq;
#endif

namespace NetworkCommsDotNet.DPSBase
{    
    /// <summary>
    /// Provides methods that convert an <see cref="object"/> into a <see cref="byte"/>[]
    /// </summary>
    public abstract class DataSerializer
    {
        private static Dictionary<Type, byte> cachedIdentifiers = new Dictionary<Type, byte>();
        private static object locker = new object();

        /// <summary>
        /// Helper function to allow a <see cref="DataSerializer"/> to be implemented as a singleton.  Returns the singleton instance generated by the <see cref="DPSManager"/>
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> of the <see cref="DataSerializer"/> to retrieve from the <see cref="DPSManager"/></typeparam>
        /// <returns>The singleton instance generated by the <see cref="DPSManager"/></returns>
        protected static T GetInstance<T>() where T : DataSerializer
        {
            //this forces helper static constructor to be called
            T instance = DPSManager.GetDataSerializer<T>() as T;

            if (instance == null)
            {
                //if the instance is null the type was not added as part of composition
                //create a new instance of T and add it to helper as a serializer
#if NETFX_CORE
                var construct = (from constructor in typeof(T).GetTypeInfo().DeclaredConstructors
                                 where constructor.GetParameters().Length == 0
                                 select constructor).FirstOrDefault();
#else
                var construct = typeof(T).GetConstructor(new Type[] { });
                if (construct == null)
                    construct = typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
#endif
                if (construct == null)
                    throw new Exception();

                instance = construct.Invoke(new object[] { }) as T;

                DPSManager.AddDataSerializer(instance);
            }

            return instance as T;
        }
            
        /// <summary>
        /// Converts objectToSerialize to an array of bytes. Uses no data processors.
        /// </summary>
        /// <typeparam name="T">Type of object to serialize</typeparam>
        /// <param name="objectToSerialise">Object to serialize</param>
        /// <returns>Serialized array of bytes</returns>
        public StreamTools.StreamSendWrapper SerialiseDataObject<T>(T objectToSerialise)
        {
            return SerialiseDataObject<T>(objectToSerialise, null, null);
        }
    
        /// <summary>
        /// Converts objectToSerialize to an array of bytes using the data processors and options provided.
        /// </summary>
        /// <typeparam name="T">Type of object to serialize</typeparam>
        /// <param name="objectToSerialise">Object to serialize</param>
        /// <param name="dataProcessors">Data processors to apply to serialised data.  These will be run in index order i.e. low index to high</param>
        /// <param name="options">Options dictionary for serialisation/data processing</param>
        /// <returns>Serialized array of bytes</returns>
        public StreamTools.StreamSendWrapper SerialiseDataObject<T>(T objectToSerialise, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            if (objectToSerialise == null) throw new ArgumentNullException("objectToSerialise");

            Type objectToSerialiseType = objectToSerialise.GetType();
            if (objectToSerialiseType == typeof(Stream))
                throw new ArgumentException("Parameter should not be of type Stream. Consider using StreamSendWrapper as send object instead.", "objectToSerialise");
            else if (objectToSerialiseType == typeof(StreamTools.StreamSendWrapper))
                return StreamSendWrapperSerializer.SerialiseStreamSendWrapper(objectToSerialise as StreamTools.StreamSendWrapper, dataProcessors, options);

            StreamTools.StreamSendWrapper baseRes = null;
     
            baseRes = ArraySerializer.SerialiseArrayObject(objectToSerialise, dataProcessors, options);

            //if the object was an array baseres will != null
            if (baseRes != null) 
                return baseRes;
            else
                return SerialiseGeneralObject<T>(objectToSerialise, dataProcessors, options);
        }

        private StreamTools.StreamSendWrapper SerialiseGeneralObject<T>(T objectToSerialise, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            //Create the first memory stream that will be used 
            MemoryStream tempStream1 = new MemoryStream();

            //Serialise the object using the overridden method
            SerialiseDataObjectInt(tempStream1, objectToSerialise, options);

            //If we have no data processing to do we can simply return the serialised bytes
            if (dataProcessors == null || dataProcessors.Count == 0)
                return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream1, true));
            else
            {
                //Otherwise we will need a second memory stream to process the data
                MemoryStream tempStream2 = new MemoryStream();

                //variable will store the number of bytes in the output stream at each processing stage
                long writtenBytes;
                //Process the serialised data using the first data processor.  We do this separately to avoid multiple seek/setLength calls for
                //the most common usage case
                dataProcessors[0].ForwardProcessDataStream(tempStream1, tempStream2, options, out writtenBytes);

                //If we have more than one processor we need to loop through them
                if (dataProcessors.Count > 1)
                {
                    //Loop through the remaining processors two at a time.  Each loop processes data temp2 -> temp1 -> temp2
                    for (int i = 1; i < dataProcessors.Count; i += 2)
                    {
                        //Seek streams to zero and truncate the last output stream to the data size
                        tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                        tempStream1.Seek(0, 0);
                        //Process the data
                        dataProcessors[i].ForwardProcessDataStream(tempStream2, tempStream1, options, out writtenBytes);

                        //if the second of the pair exists
                        if (i + 1 < dataProcessors.Count)
                        {
                            //Seek streams to zero and truncate the last output stream to the data size
                            tempStream2.Seek(0, 0);
                            tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                            //Process the data
                            dataProcessors[i + 1].ForwardProcessDataStream(tempStream1, tempStream2, options, out writtenBytes);
                        }
                    }
                }

                //Depending on whether the number of processors is even or odd a different stream will hold the final data
                if (dataProcessors.Count % 2 == 0)
                {
                    //Seek to the beginning and truncate the output stream
                    tempStream1.Seek(0, 0);
                    tempStream1.SetLength(writtenBytes);
                    //Return the resultant bytes
                    //return tempStream1.ToArray();
                    tempStream2.Dispose();
                    return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream1, true));
                }
                else
                {
                    //Seek to the beginning and truncate the output stream
                    tempStream2.Seek(0, 0);
                    tempStream2.SetLength(writtenBytes);
                    //Return the resultant bytes
                    //return tempStream2.ToArray();
                    tempStream1.Dispose();
                    return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream2, true));
                }
            }
        }

        /// <summary>
        /// Converts array of bytes previously serialized to an object of provided type. Assumes no data processors.
        /// </summary>
        /// <typeparam name="T">Type of object to deserialize to</typeparam>
        /// <param name="receivedObjectBytes">Byte array containing serialized and compressed object</param>
        /// <returns>The deserialized object</returns>
        public T DeserialiseDataObject<T>(byte[] receivedObjectBytes)
        {
            if (receivedObjectBytes == null) throw new ArgumentNullException("receivedObjectBytes");
#if NETFX_CORE
            using (var ms = new MemoryStream(receivedObjectBytes, 0, receivedObjectBytes.Length, false))
#else
            using (var ms = new MemoryStream(receivedObjectBytes, 0, receivedObjectBytes.Length, false, true))
#endif
                return DeserialiseDataObject<T>(ms);
        }

        /// <summary>
        /// Converts a memory stream containing bytes previously serialized to an object of provided type. Assumes no data processors.
        /// </summary>
        /// <typeparam name="T">Type of object to deserialize to</typeparam>
        /// <param name="receivedObjectStream">Byte array containing serialized and compressed object</param>
        /// <returns>The deserialized object</returns>
        public T DeserialiseDataObject<T>(MemoryStream receivedObjectStream)
        {
            if (receivedObjectStream == null) throw new ArgumentNullException("receivedObjectStream");

            return DeserialiseDataObject<T>(receivedObjectStream, null, null);
        }

        /// <summary>
        /// Converts bytes previously serialized and processed using data processors to an object of provided type
        /// </summary>
        /// <typeparam name="T">Type of object to deserialize to</typeparam>
        /// <param name="receivedObjectBytes">Byte array containing serialized and compressed object</param>
        /// <param name="dataProcessors">Data processors to apply to serialised data.  These will be run in reverse order i.e. high index to low</param>
        /// <param name="options">Options dictionary for serialisation/data processing</param>
        /// <returns>The deserialized object</returns>
        public T DeserialiseDataObject<T>(byte[] receivedObjectBytes, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            if (receivedObjectBytes == null) throw new ArgumentNullException("receivedObjectBytes");
            
#if NETFX_CORE
            return DeserialiseDataObject<T>(new MemoryStream(receivedObjectBytes, 0, receivedObjectBytes.Length, false), dataProcessors, options);
#else
            return DeserialiseDataObject<T>(new MemoryStream(receivedObjectBytes, 0, receivedObjectBytes.Length, false, true), dataProcessors, options);
#endif
        }

        /// <summary>
        /// Converts a memory stream containing bytes previously serialized and processed using data processors to an object of provided type
        /// </summary>
        /// <typeparam name="T">Type of object to deserialize to</typeparam>
        /// <param name="receivedObjectStream">Byte array containing serialized and compressed object</param>
        /// <param name="dataProcessors">Data processors to apply to serialised data.  These will be run in reverse order i.e. high index to low</param>
        /// <param name="options">Options dictionary for serialisation/data processing</param>
        /// <returns>The deserialized object</returns>
        public T DeserialiseDataObject<T>(MemoryStream receivedObjectStream, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            if (receivedObjectStream == null) throw new ArgumentNullException("receivedObjectStream");
            
            //Ensure the stream is at the beginning
            receivedObjectStream.Seek(0, SeekOrigin.Begin);

            //Try to deserialise using the array helper.  If the result is a primitive array this call will return an object
            object baseRes = null;
                
            baseRes = ArraySerializer.DeserialiseArrayObject(receivedObjectStream, typeof(T), dataProcessors, options);

            if (baseRes != null)
                return (T)baseRes;
            else
                return DeserialiseGeneralObject<T>(receivedObjectStream, dataProcessors, options);
        }

        private T DeserialiseGeneralObject<T>(MemoryStream receivedObjectStream, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            //Create a memory stream using the incoming bytes as the initial buffer
            MemoryStream inputStream = receivedObjectStream;

            //If no data processing is required then we can just deserialise the object straight
            if (dataProcessors == null || dataProcessors.Count == 0)
                return (T)DeserialiseDataObjectInt(inputStream, typeof(T), options);
            else
            {
                //Otherwise we will need another stream
                using (MemoryStream tempStream1 = new MemoryStream())
                {
                    //variable will store the number of bytes in the output stream at each processing stage
                    long writtenBytes;
                    //Data processing for deserialization is done in reverse so run the last element
                    dataProcessors[dataProcessors.Count - 1].ReverseProcessDataStream(inputStream, tempStream1, options, out writtenBytes);

                    //If we have more than 1 processor we will now run the remaining processors pair wise
                    if (dataProcessors.Count > 1)
                    {
                        using (MemoryStream tempStream2 = new MemoryStream())
                        {
                            //Data processing for deserialization is done in reverse so run from a high index down in steps of 2. Each loop processes data temp -> input -> temp
                            for (int i = dataProcessors.Count - 2; i >= 0; i -= 2)
                            {
                                //Seek streams to zero and truncate the last output stream to the data size
                                tempStream2.Seek(0, 0);
                                tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                //Process the data
                                dataProcessors[i].ReverseProcessDataStream(tempStream1, tempStream2, options, out writtenBytes);

                                //if the second processor exists run it
                                if (i - 1 >= 0)
                                {
                                    //Seek streams to zero and truncate the last output stream to the data size
                                    tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                                    tempStream1.Seek(0, 0);
                                    //Process the data
                                    dataProcessors[i - 1].ReverseProcessDataStream(tempStream2, tempStream1, options, out writtenBytes);
                                }
                            }

                            //Depending on whether the number of processors is even or odd a different stream will hold the final data
                            if (dataProcessors.Count % 2 == 0)
                            {
                                //Seek to the beginning and truncate the output stream
                                tempStream2.Seek(0, 0);
                                tempStream2.SetLength(writtenBytes);
                                //Return the resultant bytes
                                return (T)DeserialiseDataObjectInt(tempStream2, typeof(T), options);
                            }
                            else
                            {
                                //Seek to the beginning and truncate the output stream
                                tempStream1.Seek(0, 0);
                                tempStream1.SetLength(writtenBytes);
                                //Return the resultant bytes
                                return (T)DeserialiseDataObjectInt(tempStream1, typeof(T), options);
                            }
                        }
                    }

                    //Seek to the beginning and truncate the output stream
                    tempStream1.Seek(0, 0);
                    tempStream1.SetLength(writtenBytes);
                    //Return the resultant bytes
                    return (T)DeserialiseDataObjectInt(tempStream1, typeof(T), options);
                }
            }
        }

        /// <summary>
        /// Returns a unique identifier for the serializer type.  Used in automatic serialization/compression detection
        /// </summary>
        public byte Identifier
        {
            get
            {
                lock (locker)
                {
                    Type typeOfThis = this.GetType();

                    if (!cachedIdentifiers.ContainsKey(typeOfThis))
                    {
#if NETFX_CORE
                        var attributes = this.GetType().GetTypeInfo().GetCustomAttributes(typeof(DataSerializerProcessorAttribute), false).ToArray();
#else
                        var attributes = this.GetType().GetCustomAttributes(typeof(DataSerializerProcessorAttribute), false);
#endif
                        if (attributes.Length == 1)
                            cachedIdentifiers[typeOfThis] = (attributes[0] as DataSerializerProcessorAttribute).Identifier;
                        else
                            throw new Exception("Data serializer and processor types must have a DataSerializerProcessorAttribute specifying a unique id");
                    }

                    return cachedIdentifiers[typeOfThis];
                }
            }
        }
        
        /// <summary>
        /// Serialises an object to a stream using any relevant options provided
        /// </summary>
        /// <param name="ouputStream">The stream to serialise to</param>
        /// <param name="objectToSerialise">The object to serialise</param>
        /// <param name="options">Options dictionary for serialisation/data processing</param>
        protected abstract void SerialiseDataObjectInt(Stream ouputStream, object objectToSerialise, Dictionary<string, string> options);

        /// <summary>
        /// Deserialises the data in a stream to an object of the specified type using any relevant provided options 
        /// </summary>
        /// <param name="inputStream">The stream containing the serialised object</param>
        /// <param name="resultType">The return object Type</param>
        /// <param name="options">Options dictionary for serialisation/data processing</param>
        /// <returns>The deserialised object</returns>
        protected abstract object DeserialiseDataObjectInt(Stream inputStream, Type resultType, Dictionary<string, string> options);
    }
    
    /// <summary>
    /// Class that provides optimised method for serializing arrays of primitive data types.
    /// </summary>
    static class ArraySerializer
    {
#if WINDOWS_PHONE || iOS || NETFX_CORE

        /// <summary>
        /// Serializes objectToSerialize to a byte array using compression provided by compressor if T is an array of primitives.  Otherwise returns default value for T.  Override 
        /// to serialize other types
        /// </summary>        
        /// <param name="objectToSerialise">Object to serialize</param>
        /// <param name="dataProcessors">The compression provider to use</param>
        /// <param name="options">Options to be used during serialization and processing of data</param>
        /// <returns>The serialized and compressed bytes of objectToSerialize</returns>
        public static StreamTools.StreamSendWrapper SerialiseArrayObject(object objectToSerialise, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            Type objType = objectToSerialise.GetType();

            if (objType.IsArray)
            {
                var elementType = objType.GetElementType();

                //No need to do anything for a byte array
                if (elementType == typeof(byte) && (dataProcessors == null || dataProcessors.Count == 0))
                {
                    byte[] bytesToSerialise = objectToSerialise as byte[];
                    //return objectToSerialise as byte[];
#if NETFX_CORE
                    return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(new MemoryStream(bytesToSerialise, 0, bytesToSerialise.Length, false), true));
#else
                    return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(new MemoryStream(bytesToSerialise, 0, bytesToSerialise.Length, false, true), true));
#endif
                }
#if NETFX_CORE
                else if (elementType.GetTypeInfo().IsPrimitive)
#else
                else if (elementType.IsPrimitive)
#endif
                {
                    var asArray = objectToSerialise as Array;

#if WINDOWS_PHONE || iOS || NETFX_CORE
#else
                    GCHandle arrayHandle = GCHandle.Alloc(asArray, GCHandleType.Pinned);
#endif

                    try
                    {

#if WINDOWS_PHONE || iOS || NETFX_CORE
#else
                        IntPtr safePtr = Marshal.UnsafeAddrOfPinnedArrayElement(asArray, 0);
#endif

                        long writtenBytes = 0;


#if WINDOWS_PHONE || iOS || NETFX_CORE
                        var byteArray = new byte[asArray.Length * Marshal.SizeOf(elementType)];
                        Buffer.BlockCopy(asArray, 0, byteArray, 0, byteArray.Length);
                        MemoryStream tempStream1 = new MemoryStream();
                        tempStream1.Write(byteArray, 0, byteArray.Length);
#else
                        MemoryStream tempStream1 = new System.IO.MemoryStream();

                        using (UnmanagedMemoryStream inputDataStream = new System.IO.UnmanagedMemoryStream((byte*)safePtr, asArray.Length * Marshal.SizeOf(elementType)))
                        {
                            if (dataProcessors == null || dataProcessors.Count == 0)
                            {
                                AsyncStreamCopier.CopyStreamTo(inputDataStream, tempStream1);
                                //return tempStream1.ToArray();
                                return new StreamSendWrapper(new ThreadSafeStream(tempStream1, true));
                            }

                            dataProcessors[0].ForwardProcessDataStream(inputDataStream, tempStream1, options, out writtenBytes);
                        }
#endif

                        if (dataProcessors.Count > 1)
                        {
                            MemoryStream tempStream2 = new MemoryStream();

                            for (int i = 1; i < dataProcessors.Count; i += 2)
                            {
                                tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                tempStream2.Seek(0, 0);
                                dataProcessors[i].ForwardProcessDataStream(tempStream1, tempStream2, options, out writtenBytes);

                                if (i + 1 < dataProcessors.Count)
                                {
                                    tempStream1.Seek(0, 0);
                                    tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                                    dataProcessors[i].ForwardProcessDataStream(tempStream2, tempStream1, options, out writtenBytes);
                                }
                            }

                            if (dataProcessors.Count % 2 == 0)
                            {
                                tempStream2.SetLength(writtenBytes + 4);
                                tempStream2.Seek(writtenBytes, 0);
                                tempStream2.Write(BitConverter.GetBytes(asArray.Length), 0, sizeof(int));
                                //return tempStream2.ToArray();
                                tempStream1.Dispose();
                                return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream2, true));
                            }
                            else
                            {
                                tempStream1.SetLength(writtenBytes + 4);
                                tempStream1.Seek(writtenBytes, 0);
                                tempStream1.Write(BitConverter.GetBytes(asArray.Length), 0, sizeof(int));
                                //return tempStream1.ToArray();
                                tempStream2.Dispose();
                                return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream1, true));
                            }
                        }
                        else
                        {
                            tempStream1.SetLength(writtenBytes + 4);
                            tempStream1.Seek(writtenBytes, 0);
                            tempStream1.Write(BitConverter.GetBytes(asArray.Length), 0, sizeof(int));
                            //return tempStream1.ToArray();
                            return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream1, true));
                        }
                    }
                    finally
                    {
#if WINDOWS_PHONE || iOS || NETFX_CORE
#else
                        arrayHandle.Free();
#endif
                    }
                }
            }

            return null;
        }

#else
        /// <summary>
        /// Serializes objectToSerialize to a byte array using compression provided by compressor if T is an array of primitives.  Otherwise returns default value for T.  Override 
        /// to serialize other types
        /// </summary>        
        /// <param name="objectToSerialise">Object to serialize</param>
        /// <param name="dataProcessors">The compression provider to use</param>
        /// <param name="options">Options to be used during serialization and processing of data</param>
        /// <returns>The serialized and compressed bytes of objectToSerialize</returns>
        public static unsafe StreamTools.StreamSendWrapper SerialiseArrayObject(object objectToSerialise, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            Type objType = objectToSerialise.GetType();

            if (objType.IsArray)
            {
                var elementType = objType.GetElementType();

                //No need to do anything for a byte array
                if (elementType == typeof(byte) && (dataProcessors == null || dataProcessors.Count == 0))
                {
                    byte[] bytesToSerialise = objectToSerialise as byte[];
                    //return objectToSerialise as byte[];
                    return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(new MemoryStream(bytesToSerialise, 0, bytesToSerialise.Length, false, true), true));
                }
                else if (elementType.IsPrimitive)
                {
                    var asArray = objectToSerialise as Array;
                    GCHandle arrayHandle = GCHandle.Alloc(asArray, GCHandleType.Pinned);

                    try
                    {
                        IntPtr safePtr = Marshal.UnsafeAddrOfPinnedArrayElement(asArray, 0);
                        long writtenBytes = 0;

                        MemoryStream tempStream1 = new System.IO.MemoryStream();

                        using (UnmanagedMemoryStream inputDataStream = new System.IO.UnmanagedMemoryStream((byte*)safePtr, asArray.Length * Marshal.SizeOf(elementType)))
                        {
                            if (dataProcessors == null || dataProcessors.Count == 0)
                            {
                                StreamTools.Write(inputDataStream, tempStream1);
                                //return tempStream1.ToArray();
                                return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream1, true));
                            }

                            dataProcessors[0].ForwardProcessDataStream(inputDataStream, tempStream1, options, out writtenBytes);
                        }

                        if (dataProcessors.Count > 1)
                        {
                            MemoryStream tempStream2 = new MemoryStream();

                            for (int i = 1; i < dataProcessors.Count; i += 2)
                            {
                                tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                tempStream2.Seek(0, 0);
                                dataProcessors[i].ForwardProcessDataStream(tempStream1, tempStream2, options, out writtenBytes);

                                if (i + 1 < dataProcessors.Count)
                                {
                                    tempStream1.Seek(0, 0);
                                    tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                                    dataProcessors[i].ForwardProcessDataStream(tempStream2, tempStream1, options, out writtenBytes);
                                }
                            }

                            if (dataProcessors.Count % 2 == 0)
                            {
                                tempStream2.SetLength(writtenBytes + 4);
                                tempStream2.Seek(writtenBytes, 0);
                                tempStream2.Write(BitConverter.GetBytes(asArray.Length), 0, sizeof(int));
                                //return tempStream2.ToArray();
                                tempStream1.Dispose();
                                return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream2, true));
                            }
                            else
                            {
                                tempStream1.SetLength(writtenBytes + 4);
                                tempStream1.Seek(writtenBytes, 0);
                                tempStream1.Write(BitConverter.GetBytes(asArray.Length), 0, sizeof(int));
                                //return tempStream1.ToArray();
                                tempStream2.Dispose();
                                return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream1, true));
                            }
                        }
                        else
                        {
                            tempStream1.SetLength(writtenBytes + 4);
                            tempStream1.Seek(writtenBytes, 0);
                            tempStream1.Write(BitConverter.GetBytes(asArray.Length), 0, sizeof(int));
                            //return tempStream1.ToArray();
                            return new StreamTools.StreamSendWrapper(new StreamTools.ThreadSafeStream(tempStream1, true));
                        }
                    }
                    finally
                    {
                        arrayHandle.Free();
                    }
                }
            }

            return null;
        }
#endif

#if WINDOWS_PHONE || iOS || NETFX_CORE
        /// <summary>
        /// Deserializes data object held as compressed bytes in receivedObjectBytes using compressor if desired type is an array of primitives
        /// </summary>        
        /// <param name="inputStream">Byte array containing serialized and compressed object</param>
        /// <param name="dataProcessors">Compression provider to use</param>
        /// <param name="objType">The <see cref="System.Type"/> of the <see cref="object"/> to be returned</param>
        /// <param name="options">Options to be used during deserialization and processing of data</param>
        /// <returns>The deserialized object if it is an array, otherwise null</returns>
        public static object DeserialiseArrayObject(MemoryStream inputStream, Type objType, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            if (objType.IsArray)
            {
                var elementType = objType.GetElementType();

                //No need to do anything for a byte array
                if (elementType == typeof(byte) && (dataProcessors == null || dataProcessors.Count == 0))
                {
                    try
                    {
#if NETFX_CORE
                        return (object)inputStream.ToArray();
#else
                        return (object)inputStream.GetBuffer();
#endif
                    }
                    catch (UnauthorizedAccessException)
                    {
                        return (object)inputStream.ToArray();
                    }
                }

#if NETFX_CORE
                if (elementType.GetTypeInfo().IsPrimitive)
#else
                if (elementType.IsPrimitive)
#endif
                {
                    int numElements;

                    if (dataProcessors == null || dataProcessors.Count == 0)
                        numElements = (int)(inputStream.Length / Marshal.SizeOf(elementType));
                    else
                    {
                        byte[] temp = new byte[sizeof(int)];                        
                        inputStream.Seek(inputStream.Length - sizeof(int), SeekOrigin.Begin);
                        inputStream.Read(temp, 0, sizeof(int));
                        numElements = (int)(BitConverter.ToUInt32(temp, 0));
                    }

                    Array resultArray = Array.CreateInstance(elementType, numElements);

#if WINDOWS_PHONE || iOS || NETFX_CORE
                    byte[] resultBytes = null;
#else
                    GCHandle arrayHandle = GCHandle.Alloc(resultArray, GCHandleType.Pinned);
#endif

                    try
                    {
#if WINDOWS_PHONE || iOS || NETFX_CORE
#else
                        IntPtr safePtr = Marshal.UnsafeAddrOfPinnedArrayElement(resultArray, 0);
#endif

                        long writtenBytes = 0;

#if WINDOWS_PHONE || iOS || NETFX_CORE
                        resultBytes = new byte[numElements * Marshal.SizeOf(elementType)];
                        using (System.IO.MemoryStream finalOutputStream = new MemoryStream(resultBytes))
                        {

#else
                        using (System.IO.UnmanagedMemoryStream finalOutputStream = new System.IO.UnmanagedMemoryStream((byte*)safePtr, resultArray.Length * Marshal.SizeOf(elementType), resultArray.Length * Marshal.SizeOf(elementType), System.IO.FileAccess.ReadWrite))
                        {
#endif
                            MemoryStream inputBytesStream = null;
                            try
                            {
#if NETFX_CORE
                                inputBytesStream = new MemoryStream(inputStream.ToArray(), 0, (int)(inputStream.Length - ((dataProcessors == null || dataProcessors.Count == 0) ? 0 : sizeof(int))));
#else
                                //We hope that the buffer is publicly accessible as otherwise it defeats the point of having a special serializer for arrays
                                inputBytesStream = new MemoryStream(inputStream.GetBuffer(), 0, (int)(inputStream.Length - ((dataProcessors == null || dataProcessors.Count == 0) ? 0 : sizeof(int))));                                
#endif
                            }
                            catch (UnauthorizedAccessException)
                            {
                                inputBytesStream = new MemoryStream(inputStream.ToArray(), 0, (int)(inputStream.Length - ((dataProcessors == null || dataProcessors.Count == 0) ? 0 : sizeof(int))));
                            }

                            using (inputBytesStream)
                            {
                                if (dataProcessors != null && dataProcessors.Count > 1)
                                {
                                    using (MemoryStream tempStream1 = new MemoryStream())
                                    {
                                        dataProcessors[dataProcessors.Count - 1].ReverseProcessDataStream(inputBytesStream, tempStream1, options, out writtenBytes);

                                        if (dataProcessors.Count > 2)
                                        {
                                            using (MemoryStream tempStream2 = new MemoryStream())
                                            {
                                                for (int i = dataProcessors.Count - 2; i > 0; i -= 2)
                                                {
                                                    tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                                    tempStream2.Seek(0, 0);
                                                    dataProcessors[i].ReverseProcessDataStream(tempStream1, tempStream2, options, out writtenBytes);

                                                    if (i - 1 > 0)
                                                    {
                                                        tempStream1.Seek(0, 0);
                                                        tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                                                        dataProcessors[i - 1].ReverseProcessDataStream(tempStream2, tempStream1, options, out writtenBytes);
                                                    }
                                                }

                                                if (dataProcessors.Count % 2 == 0)
                                                {
                                                    tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                                    dataProcessors[0].ReverseProcessDataStream(tempStream1, finalOutputStream, options, out writtenBytes);
                                                }
                                                else
                                                {
                                                    tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                                                    dataProcessors[0].ReverseProcessDataStream(tempStream2, finalOutputStream, options, out writtenBytes);
                                                }
                                            }
                                        }
                                        else
                                        {
                                            tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                            dataProcessors[0].ReverseProcessDataStream(tempStream1, finalOutputStream, options, out writtenBytes);
                                        }
                                    }
                                }
                                else
                                {
                                    if (dataProcessors != null && dataProcessors.Count == 1)
                                        dataProcessors[0].ReverseProcessDataStream(inputBytesStream, finalOutputStream, options, out writtenBytes);
                                    else
                                        StreamTools.Write(inputBytesStream, finalOutputStream);
                                }
                            }
                        }
                    }
                    finally
                    {
#if WINDOWS_PHONE || iOS || NETFX_CORE
                        Buffer.BlockCopy(resultBytes, 0, resultArray, 0, resultBytes.Length);
#else
                        arrayHandle.Free();
#endif
                    }

                    return (object)resultArray;
                }
            }

            return null;
        }

#else

        /// <summary>
        /// Deserializes data object held as compressed bytes in receivedObjectBytes using compressor if desired type is an array of primitives
        /// </summary>        
        /// <param name="inputStream">Byte array containing serialized and compressed object</param>
        /// <param name="dataProcessors">Compression provider to use</param>
        /// <param name="objType">The <see cref="System.Type"/> of the <see cref="object"/> to be returned</param>
        /// <param name="options">Options to be used during deserialization and processing of data</param>
        /// <returns>The deserialized object if it is an array, otherwise null</returns>
        public static unsafe object DeserialiseArrayObject(MemoryStream inputStream, Type objType, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            if (objType.IsArray)
            {
                var elementType = objType.GetElementType();

                //No need to do anything for a byte array
                if (elementType == typeof(byte) && (dataProcessors == null || dataProcessors.Count == 0))
                {
                    try
                    {
                        return (object)inputStream.GetBuffer();
                    }
                    catch (UnauthorizedAccessException)
                    {
                        return (object)inputStream.ToArray();
                    }
                }

                if (elementType.IsPrimitive)
                {
                    int numElements;

                    if (dataProcessors == null || dataProcessors.Count == 0)
                        numElements = (int)(inputStream.Length / Marshal.SizeOf(elementType));
                    else
                    {
                        if (inputStream.Length < sizeof(int))
                            throw new SerialisationException("Error deserializing to type " + objType +". Input stream length too short to determine number of elements.");

                        byte[] temp = new byte[sizeof(int)];                        
                        inputStream.Seek(inputStream.Length - sizeof(int), SeekOrigin.Begin);
                        inputStream.Read(temp, 0, sizeof(int));
                        numElements = (int)(BitConverter.ToUInt32(temp, 0));
                    }

                    Array resultArray = Array.CreateInstance(elementType, numElements);
                    GCHandle arrayHandle = GCHandle.Alloc(resultArray, GCHandleType.Pinned);

                    try
                    {
                        IntPtr safePtr = Marshal.UnsafeAddrOfPinnedArrayElement(resultArray, 0);
                        long writtenBytes = 0;

                        using (System.IO.UnmanagedMemoryStream finalOutputStream = new System.IO.UnmanagedMemoryStream((byte*)safePtr, resultArray.Length * Marshal.SizeOf(elementType), resultArray.Length * Marshal.SizeOf(elementType), System.IO.FileAccess.ReadWrite))
                        {
                            MemoryStream inputBytesStream = null;
                            try
                            {
                                //We hope that the buffer is publicly accessible as otherwise it defeats the point of having a special serializer for arrays
                                inputBytesStream = new MemoryStream(inputStream.GetBuffer(), 0, (int)(inputStream.Length - ((dataProcessors == null || dataProcessors.Count == 0) ? 0 : sizeof(int))));
                            }
                            catch (UnauthorizedAccessException)
                            {
                                inputBytesStream = new MemoryStream(inputStream.ToArray(), 0, (int)(inputStream.Length - ((dataProcessors == null || dataProcessors.Count == 0) ? 0 : sizeof(int))));
                            }

                            using (inputBytesStream)
                            {
                                if (dataProcessors != null && dataProcessors.Count > 1)
                                {
                                    using (MemoryStream tempStream1 = new MemoryStream())
                                    {
                                        dataProcessors[dataProcessors.Count - 1].ReverseProcessDataStream(inputBytesStream, tempStream1, options, out writtenBytes);

                                        if (dataProcessors.Count > 2)
                                        {
                                            using (MemoryStream tempStream2 = new MemoryStream())
                                            {
                                                for (int i = dataProcessors.Count - 2; i > 0; i -= 2)
                                                {
                                                    tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                                    tempStream2.Seek(0, 0);
                                                    dataProcessors[i].ReverseProcessDataStream(tempStream1, tempStream2, options, out writtenBytes);

                                                    if (i - 1 > 0)
                                                    {
                                                        tempStream1.Seek(0, 0);
                                                        tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                                                        dataProcessors[i - 1].ReverseProcessDataStream(tempStream2, tempStream1, options, out writtenBytes);
                                                    }
                                                }

                                                if (dataProcessors.Count % 2 == 0)
                                                {
                                                    tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                                    dataProcessors[0].ReverseProcessDataStream(tempStream1, finalOutputStream, options, out writtenBytes);
                                                }
                                                else
                                                {
                                                    tempStream2.Seek(0, 0); tempStream2.SetLength(writtenBytes);
                                                    dataProcessors[0].ReverseProcessDataStream(tempStream2, finalOutputStream, options, out writtenBytes);
                                                }
                                            }
                                        }
                                        else
                                        {
                                            tempStream1.Seek(0, 0); tempStream1.SetLength(writtenBytes);
                                            dataProcessors[0].ReverseProcessDataStream(tempStream1, finalOutputStream, options, out writtenBytes);
                                        }
                                    }
                                }
                                else
                                {
                                    if (dataProcessors != null && dataProcessors.Count == 1)
                                        dataProcessors[0].ReverseProcessDataStream(inputBytesStream, finalOutputStream, options, out writtenBytes);
                                    else
                                        StreamTools.Write(inputBytesStream, finalOutputStream);
                                }
                            }
                        }
                    }
                    finally
                    {
                        arrayHandle.Free();
                    }

                    return (object)resultArray;
                }
            }

            return null;
        }
#endif

    }
    
    /// <summary>
    /// Class that provides optimised method for serializing arrays of primitive data types.
    /// </summary>
    static class StreamSendWrapperSerializer
    {
        /// <summary>
        /// Serializes StreamSendWrapper to a StreamSendWrapper possibly using provided data processors.  If there are no data processor streamSendWrapperToSerialize will be returned.
        /// </summary>        
        /// <param name="streamSendWrapperToSerialize">StreamSendWrapper to serialize</param>
        /// <param name="dataProcessors">The compression provider to use</param>
        /// <param name="options">Options to be used during serialization and processing of data</param>
        /// <returns>The serialized and compressed bytes of objectToSerialize</returns>
        public static StreamTools.StreamSendWrapper SerialiseStreamSendWrapper(StreamTools.StreamSendWrapper streamSendWrapperToSerialize, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            //If we have no data processing to do we can simply return the serialised bytes
            if (dataProcessors == null || dataProcessors.Count == 0)
                return streamSendWrapperToSerialize;

            var array = new byte[streamSendWrapperToSerialize.Length];
            using (MemoryStream tempStream = new MemoryStream(array))
                streamSendWrapperToSerialize.ThreadSafeStream.CopyTo(tempStream, streamSendWrapperToSerialize.Start, streamSendWrapperToSerialize.Length, 8000);

            return ArraySerializer.SerialiseArrayObject(array, dataProcessors, options);            
        }

        /// <summary>
        /// Deserializes data object held as compressed bytes in receivedObjectBytes using compressor if desired type is a <see cref="StreamTools.StreamSendWrapper"/>
        /// </summary>        
        /// <param name="receivedObjectBytes">Byte array containing serialized and compressed object</param>
        /// <param name="dataProcessors">Compression provider to use</param>
        /// <param name="objType">The <see cref="System.Type"/> of the <see cref="object"/> to be returned</param>
        /// <param name="options">Options to be used during deserialization and processing of data</param>
        /// <returns>The deserialized object if it is an array, otherwise null</returns>
        public static object DeserialiseStreamSendWrapper(byte[] receivedObjectBytes, Type objType, List<DataProcessor> dataProcessors, Dictionary<string, string> options)
        {
            throw new Exception("NetworkComms.Net should not be expecting an incoming type of StreamSendWrapper. Any data sent using a StreamSendWrapper should be handled on receive as byte[].");
        }
    }
}
